#include "graphicdata.h"

#include "../../editors/items/editoritemcommon.h"
#include "../../editors/items/graphicitemcommon.h"
#include "../../undo/graphicdatacommands.h"
#include "../../utils/connectionfactory.h"
#include "../../utils/undomanager.h"

#include <ieditoritem.h>
#include <igraphicitemcontent.h>
#include <settings.h>

#include <QtGui/QGraphicsItem>
#include <QtGui/QGraphicsSceneMouseEvent>
#include <QtGui/QGraphicsView>

using namespace core;
using namespace swp;

GraphicData::GraphicData() : QGraphicsScene(), IDocumentData(),
    GraphicDataModel(),
    m_previousSelfConnect(false),
    m_snapToGrid(Settings::getInstance()->optionValue(
        "Modeling", "SnapToGrid").toBool()),
    m_ignoreSnapFlag(false),
    m_gridStep(10),
    m_gridPen(QColor(200, 200, 255, 125)),
    m_palette(0),
    m_state(IEditor::SelectMove),
    m_connectionData(CONNECTION_NOT_VALID),
    m_potentialConnection(0),
    m_allConnections(),
    m_connections(),
    m_idToInstanceMapping(),
    m_snapToGridExpcetions(),
    m_registeredForResizeNotification()
{
    connect(this, SIGNAL(selectionChanged()),
        this, SLOT(handleSelectionChange()));
}

GraphicData::~GraphicData()
{
    delete m_palette;
}

IDocumentData::Type GraphicData::type() const
{
    return GraphicModel;
}

void GraphicData::setConnectionData(const ConnectionData& data)
{
    m_connectionData = data;
}

const ConnectionData& GraphicData::connectionData() const
{
    return m_connectionData;
}

bool GraphicData::supportsConnections() const
{
    return true;
}

QList<ConnectionData> GraphicData::connections(
    const IEditorItem* item) const
{
    QList<ConnectionData> ret;

    if (m_connections.contains(item))
    {
        ret = m_connections.value(item);
    }

    return ret;
}

void GraphicData::clearData(const QList<IEditorItem* >& preserve)
{
    clearModel();

    m_connections.clear();
    m_allConnections.clear();
    m_idToInstanceMapping.clear();
    m_potentialConnection = 0;
    m_connectionData = ConnectionData(CONNECTION_NOT_VALID);
    m_previousSelfConnect = false;

    if (preserve.isEmpty())
    {
        clear();
    }
    else
    {
        foreach (IGraphicEditorItem* item, graphicItems())
        {
            if (!preserve.contains(item))
            {
                delete item;
            }
        }
    }
}

QList<ConnectionData> GraphicData::connections() const
{
    return m_allConnections;
}

ConnectionData GraphicData::connectionData(
    IGraphicEditorItem* connector) const
{
    ConnectionData ret(CONNECTION_NOT_VALID, 0, 0, 0);

    QList<ConnectionData> connections;

    if (isConnectorItem(connector->type()) &&
        m_connections.contains(connector))
    {
        connections = m_connections.value(connector);

        Q_ASSERT(connections.size() == 1); // should have only one connection since it is a connector

        ret = connections.first();
    }

    return ret;
}

void GraphicData::makeConnection(uint connectionId,
    const QUuid& connector, const QUuid& from, const QUuid& to)
{
    IEditorItem* _connector = m_idToInstanceMapping.contains(connector) ?
        m_idToInstanceMapping[connector] : 0;
    IEditorItem* _from = m_idToInstanceMapping.contains(from) ?
        m_idToInstanceMapping[from] : 0;
    IEditorItem* _to = m_idToInstanceMapping.contains(to) ?
        m_idToInstanceMapping[to] : 0;

    if (connectionId != CONNECTION_NOT_VALID && _connector != 0 && _from != 0
        && _to != 0)
    {
        ConnectionData data(connectionId, _connector, _from, _to);

        if (!m_allConnections.contains(data))
        {
            addModelConnection(data);

            addConnection(_connector, data);
            addConnection(_from, data);
            addConnection(_to, data);

            _connector->connectionsRefreshed();
            _from->connectionsRefreshed();
            _to->connectionsRefreshed();
        }
    }
}

IEditor::State GraphicData::currentState() const
{
    return m_state;
}

void GraphicData::setCurrentState(IEditor::State state)
{
    if (state != m_state)
    {
        const IEditor::State previousState = m_state;
        m_state = state;

        if (state != IEditor::Connect && previousState != IEditor::Connect)
        {
            foreach (IGraphicEditorItem* item, selectedGraphicItems())
            {
                Q_ASSERT(item != 0);

                item->editorModeChange();
            }
        }
        else
        {
            foreach (IGraphicEditorItem* item, graphicItems())
            {
                item->editorModeChange();
            }
        }
    }
}

void GraphicData::scaleSelectionToFitContent()
{
    foreach (IGraphicEditorItem* item, selectedGraphicItems())
    {
        Q_ASSERT(item != 0);

        item->scaleToFitContent();
    }
}

QList<IGraphicEditorItem* > GraphicData::selectedGraphicItems() const
{
    QList<IGraphicEditorItem* > ret;

    foreach (QGraphicsItem* item, selectedItems())
    {
        IGraphicEditorItem* _item = dynamic_cast<IGraphicEditorItem* >(
            item);

        if (_item != 0 && _item->type() != CONNECTOR_HANDLE_ITEM)
        {
            Q_ASSERT(_item != 0);

            ret.append(_item);
        }
    }

    return ret;
}

void GraphicData::addGraphicItem(IGraphicEditorItem* item)
{
    item->setPalette(m_palette);

    if (m_snapToGrid)
    {
        QGraphicsItem* _item = dynamic_cast<QGraphicsItem*>(item);

        if (_item != 0)
        {
            _item->setFlag(QGraphicsItem::ItemSendsGeometryChanges,
                true);
            _item->setPos(snapToGrid(_item,
                QPointF(_item->pos().x() - 1, _item->pos().y() - 1),
                _item->pos(), false, item, m_ignoreSnapFlag));
        }
    }


    QGraphicsItem* _item = dynamic_cast<QGraphicsItem* >(
        item);

    if (_item != 0)
    {
        if (!m_idToInstanceMapping.contains(item->id()))
        {
            m_idToInstanceMapping[item->id()] = item;
        }

        addModelItem(item);
        addItem(_item);
        item->setParentDocumentData(this);
        emit dataChanged();
    }
}

void GraphicData::removeGraphicItem(IGraphicEditorItem* item,
    bool keepInstance)
{
    QGraphicsItem* _item = dynamic_cast<QGraphicsItem* >(
        item);

    if (_item != 0 && m_idToInstanceMapping.contains(item->id()))
    {
        m_idToInstanceMapping.remove(item->id());

        /*
        if (m_connections.contains(item))
        {
            const QList<ConnectionData> connections =
                m_connections.value(item);

            QVector<IEditorItem* > toBeNotified;

            foreach (const ConnectionData& data, connections)
            {
                QGraphicsItem* _connector = dynamic_cast<QGraphicsItem* >(
                        data.Connector);

                if (_connector != 0 && data.Connector != item)
                {
                    removeItem(_connector);
                    delete data.Connector;

                    IEditorItem* notifee = data.From == item ?
                        data.To : data.From;
                    toBeNotified.append(notifee);
                }
                else if (_connector != 0)
                {
                    toBeNotified.append(data.From);
                    toBeNotified.append(data.To);
                }

                removeConnection(data.From, data);
                removeConnection(data.To, data);
                removeConnection(data.Connector, data);
            }

            foreach (IEditorItem* notifee, toBeNotified)
            {
                notifee->connectionsRefreshed();
            }            
        }
        */

        removeModelItem(item);
        removeItem(_item);
        if (!keepInstance)
        {
            if (m_registeredForResizeNotification.contains(item))
            {
                m_registeredForResizeNotification.removeAll(item);
            }

            delete item;
        }
        else
        {
            item->setParentDocumentData(0);
        }

        emit dataChanged();
    }
}

QList<IGraphicEditorItem* > GraphicData::graphicItems() const
{
    QList<IGraphicEditorItem* > ret;

    const QList<QGraphicsItem* > items = this->items();

    foreach (QGraphicsItem* item, items)
    {
        if (item->parentItem() == 0)
        {
            IGraphicEditorItem* _item =
                dynamic_cast<IGraphicEditorItem* >(item);

            if (_item != 0) // exception for sim marks
            {
                ret.append(_item);
            }
        }
    }

    return ret;
}

const Palette* GraphicData::graphicPalette() const
{
    return m_palette;
}

void GraphicData::setGraphicPalette(const Palette* const palette)
{
    delete m_palette;
    m_palette = Palette::defaultPalette();
    *m_palette = *palette;

    // TODO: possible refresh of all items
}

bool GraphicData::inUnfinishedConnection() const
{
    bool ret = m_connectionData.ConnectionId != CONNECTION_NOT_VALID
        && m_connectionData.From != 0;

    return ret;
}

void GraphicData::cancelUnfinishedConnection(bool completeReset)
{
    resetConnectionData(completeReset);

    if (m_potentialConnection != 0)
    {
        removeItem(m_potentialConnection);
        delete m_potentialConnection;
        m_potentialConnection = 0;
    }
}

void GraphicData::removeLastConnectorAnchor()
{
    if (!m_potentialConnection->anchors().isEmpty())
    {
        m_potentialConnection->removeAnchorPoint(
            m_potentialConnection->anchors().last());
    }
}

void GraphicData::emitDataChanged()
{
    emit dataChanged();
}

void GraphicData::snapCursor(const QPointF& point)
{
    const QList<QGraphicsView* > _views = views();

    // TODO: scene can have multiple views
    QGraphicsView* view = _views.first();
    QCursor::setPos(view->mapToGlobal(
        view->mapFromScene(point)));
}

QPointF GraphicData::snapToGrid(QGraphicsItem* item, const QPointF& oldPos,
    const QPointF& newPos, bool force, IGraphicEditorItem* gItem,
    bool ignoreFlag)
{
    QPointF ret = newPos;

    const qreal xMovement = newPos.x() - oldPos.x();
    const qreal yMovement = newPos.y() - oldPos.y();
    const bool moved = xMovement != 0 || yMovement != 0;

    if (!ignoreFlag && (moved || force) &&
        (gItem == 0 || (gItem != 0 && !m_snapToGridExpcetions.contains(
            gItem->type()))))
    {
        const qreal x = newPos.x();
        const qreal y = newPos.y();

        const qreal gX = int(x/m_gridStep) * m_gridStep;
        const qreal gY = int(y/m_gridStep) * m_gridStep;

        bool changedX = false;
        bool changedY = false;

        if (x >= (gX + m_gridStep/3))
        {
            ret.rx() = gX;
            changedX = true;
        }
        else if (x <= (gX + m_gridStep - m_gridStep/3))
        {
            ret.rx() = gX + m_gridStep;
            changedX = true;
        }

        if (y >= (gY + m_gridStep/3))
        {
            ret.ry() = gY;
            changedY = true;
        }
        else if (y <= (gY + m_gridStep - m_gridStep/3))
        {
            ret.ry() = gY + m_gridStep;
            changedY = true;
        }

        if (changedX || changedY)
        {
            ret.rx() = (x - gX) < (gX + m_gridStep - x) ?
                gX : gX + m_gridStep;

            ret.ry() = (y - gY) < (gY + m_gridStep - y) ?
                gY : gY + m_gridStep;
        }
    }

    return ret;
}

bool GraphicData::snapToGridEnabled() const
{
    return m_snapToGrid; // TODO: read from settings
}

void GraphicData::setSnapToGrid(bool enabled)
{
    if (m_snapToGrid != enabled)
    {
        m_snapToGrid = enabled;

        QList<IGraphicEditorItem*> items = graphicItems();

        foreach (IGraphicEditorItem* item, items)
        {
            item->ensureHandlesGeometryChanges(enabled);
        }
    }
}

void GraphicData::addSnapToGridException(int type)
{
    if (!m_snapToGridExpcetions.contains(type))
    {
        m_snapToGridExpcetions.append(type);
    }
}

void GraphicData::expandIfNeeded(QGraphicsItem* item, const QPointF& newPos)
{
    const QRectF bRect = item->boundingRect();
    const qreal rX = newPos.x() + bRect.width();
    const qreal rY = newPos.y() + bRect.height();

    const QRectF _sceneRect = sceneRect();

    if (rX > _sceneRect.width()
        || rY > _sceneRect.height())
    {
        setSceneRect(0, 0,
            rX > _sceneRect.width() ? rX + 15 : _sceneRect.width(),
            rY > _sceneRect.height() ? rY + 15 : _sceneRect.height());

        foreach (IGraphicEditorItem* item, m_registeredForResizeNotification)
        {
            item->handleSceneResize();
        }
    }
}

void GraphicData::registerForResizeNotification(
    core::IGraphicEditorItem* item)
{
    if (!m_registeredForResizeNotification.contains(item))
    {
        m_registeredForResizeNotification.append(item);
    }
}

void GraphicData::unregisterForResizeNotification(
    core::IGraphicEditorItem* item)
{
    if (m_registeredForResizeNotification.contains(item))
    {
        m_registeredForResizeNotification.removeAll(item);
    }
}

void GraphicData::setIgnoreSnapFlag(bool ignoreSnapFlag)
{
    m_ignoreSnapFlag = ignoreSnapFlag;
}

void GraphicData::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    if (m_state != IEditor::Connect)
    {
        QGraphicsScene::mousePressEvent(event);
    }
}

void GraphicData::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    if (m_state == IEditor::Connect && m_potentialConnection != 0)
    {
        const qreal x = event->scenePos().x();
        const qreal y = event->scenePos().y();

        const QList<QGraphicsItem* > _items = items(QPointF(x, y));

        bool adjustFlag = false;

        if (_items.size() >= 1)
        {
            QGraphicsItem* item = 0;

            foreach (QGraphicsItem* _item, _items)
            {
                if (!isConnectorHandleItem(_item->type()) &&
                    !isConnectorItem(_item->type()) &&
                    isSwpItem(_item->type()))
                {
                    item = _item;
                    break;
                }
            }

            if (item != 0)
            {
                if (item->parentItem() != 0)
                {
                    while (item->parentItem() != 0)
                    {
                        item = item->parentItem();
                    }
                }

                IGraphicEditorItem* _item = dynamic_cast<IGraphicEditorItem*>(
                    item);

                Q_ASSERT(_item != 0); // cycle above should guarentee us that we will get the GraphicEditorItem

                m_connectionData.To = _item;

                if (m_connectionData.To == m_connectionData.From)
                {
                    _item->handleSelfConnect(m_potentialConnection,
                        QPointF(x,y), m_previousSelfConnect);

                    m_previousSelfConnect = true;
                }
                else if (m_previousSelfConnect)
                {
                    m_potentialConnection->removeAnchorPoints();

                    m_previousSelfConnect = false;
                }
            }
            else
            {
                if (m_previousSelfConnect)
                {
                    m_potentialConnection->removeAnchorPoints();
                    m_previousSelfConnect = false;
                }

                m_connectionData.To = 0;
                adjustFlag = true;
            }
        }
        else
        {
            if (m_previousSelfConnect)
            {
                m_potentialConnection->removeAnchorPoints();
                m_previousSelfConnect = false;
            }

            m_connectionData.To = 0;
            adjustFlag = true;
        }

        QPointF pos = event->scenePos();

        if (adjustFlag)
        {
            if (event->modifiers() == Qt::ShiftModifier && m_snapToGrid)
            {
                pos = snapToGrid(0, pos, pos, true);
            }
        }

        m_potentialConnection->setEndPoint(pos);
    }

    QGraphicsScene::mouseMoveEvent(event);
}

void GraphicData::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    if (m_state != IEditor::Connect)
    {
        QGraphicsScene::mouseReleaseEvent(event);
    } else {
        if (m_connectionData.From == 0)
        {
            const qreal x = event->scenePos().x();
            const qreal y = event->scenePos().y();

            QGraphicsItem* item = itemAt(x, y);
            if (item != 0)
            {
                if (item->parentItem() != 0)
                {
                    while (item->parentItem() != 0)
                    {
                        item = item->parentItem();
                    }
                }

                IGraphicEditorItem* _item = dynamic_cast<IGraphicEditorItem*>(
                    item);

                Q_ASSERT(_item != 0); // cycle above should guarentee us that we will get the GraphicEditorItem

                if (_item->supportsConnection(m_connectionData))
                {
                    m_connectionData.From = _item;
                    m_connectionData.To = _item;

                    m_potentialConnection = ConnectionFactory::getInstance()->
                        graphicItemForConnection(m_connectionData.ConnectionId);
                    m_potentialConnection->setParentDocumentData(this);

                    m_connectionData.Connector = m_potentialConnection;

                    Q_ASSERT(m_potentialConnection != 0); // Connection item was added to gallery but not registered

                    const QPointF position = _item->nearestConnectionPoint(
                        QPointF(x, y), Outgoing);

                    m_potentialConnection->setStartPoint(position);
                    m_potentialConnection->setEndPoint(position);

                    addItem(m_potentialConnection);

                    _item->handleSelfConnect(m_potentialConnection,
                        QPointF(x, y));
                }
                else
                {
                    m_connectionData.From = 0;
                }
            }
        }
        else
        {
            const qreal x = event->scenePos().x();
            const qreal y = event->scenePos().y();

            const QList<QGraphicsItem* > _items = items(QPointF(x, y));

            QGraphicsItem* item = 0;

            foreach (QGraphicsItem* _item, _items)
            {
                if (_item != m_potentialConnection &&
                    !m_potentialConnection->isAncestorOf(_item) &&
                    _item->type() != CONNECTOR_HANDLE_ITEM)
                {
                    item = _item;
                    break;
                }
            }

            if (item == 0)
            {
                if (event->modifiers() == Qt::ShiftModifier)
                {
                    QPointF pos(x, y);

                    if (Qt::ShiftModifier && m_snapToGrid)
                    {
                        pos = snapToGrid(0, pos, pos, true);
                    }

                    m_potentialConnection->addAnchorPoint(
                        pos);
                }
            }
            else
            {
                if (item->parentItem() != 0)
                {
                    while (item->parentItem() != 0)
                    {
                        item = item->parentItem();
                    }
                }

                IGraphicEditorItem* _item = dynamic_cast<IGraphicEditorItem*>(
                    item);

                Q_ASSERT(_item != 0); // cycle above should guarentee us that we will get the GraphicEditorItem

                m_connectionData.To = _item;

                if (_item->supportsConnection(m_connectionData))
                {
                    const QPointF position = _item->nearestConnectionPoint(
                        QPointF(x, y), Incoming);

                    m_potentialConnection->setEndPoint(position);

                    AddConnectionCommand* command =
                        new AddConnectionCommand(
                            this, m_potentialConnection, m_connectionData);

                    UndoManager::getInstance()->push(this, command);

                    m_connectionData.From = 0;
                    m_connectionData.To = 0;
                    m_connectionData.Connector = 0;

                    if (!m_idToInstanceMapping.contains(
                        m_potentialConnection->id()))
                    {
                        m_idToInstanceMapping[m_potentialConnection->id()] =
                            m_potentialConnection;
                    }

                    m_potentialConnection = 0; // connection goes from potential to persistent

                    emit dataChanged();
                }
                else
                {
                    m_connectionData.To = 0;
                }
            }
        }
    }
}

void GraphicData::drawBackground(QPainter* painter,
    const QRectF& rect)
{
    painter->setPen(m_gridPen);

    const QRectF _sceneRect = sceneRect();

    const qreal top = qMax(rect.top(), _sceneRect.top());
    const qreal left = qMax(rect.left(), _sceneRect.left());
    const qreal bottom = qMin(rect.bottom(), _sceneRect.bottom());
    const qreal right = qMin(rect.right(), _sceneRect.right());

    qreal start = round(top, m_gridStep);

    if (start > top) {
        start -= m_gridStep;
    }

    for (qreal y = start - m_gridStep; y < bottom; ) {
        y += m_gridStep;
        painter->drawLine(left, y, right, y);
    }    

    start = round(left, m_gridStep);

    if (start > left) {
        start -= m_gridStep;
    }

    for (qreal x = start - m_gridStep; x < right; ) {
        x += m_gridStep;
        painter->drawLine(x, top, x, bottom);
    }
}

void GraphicData::resetConnectionData(bool completeReset)
{
    if (completeReset)
    {
        m_connectionData.ConnectionId = CONNECTION_NOT_VALID;
    }

    m_connectionData.From = 0;
    m_connectionData.To = 0;
    m_connectionData.Connector = 0;
}

void GraphicData::addConnection(const IEditorItem* item,
    const ConnectionData& data)
{
    if (!m_allConnections.contains(data))
    {
        m_allConnections.append(data);
    }

    if (m_connections.contains(item))
    {
        if (!m_connections[item].contains(data))
        {
            m_connections[item].append(data);
        }
    }
    else
    {
        QList<ConnectionData> list;
        list.append(data);

        m_connections[item] = list;
    }
}

void GraphicData::removeConnection(const IEditorItem* item,
    const ConnectionData& data)
{
    if (m_connections.contains(item))
    {
        if (m_connections[item].contains(data))
        {
            m_connections[item].removeOne(data);

            if (m_connections[item].size() == 0)
            {
                if (m_allConnections.contains(data))
                {
                    m_allConnections.removeAll(data);
                }

                m_connections.remove(item);
            }
        }
    }
}

//private slots
void GraphicData::handleSelectionChange()
{
    const QList<IGraphicEditorItem*> items = selectedGraphicItems();

    emit editorItemSelectionChanged(items.size() > 0 ? items.last() : 0);
}
